#include <argp.h>
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>
#define MAXSIZE 1<<10

FILE *in, *out;

char in_buffer[MAXSIZE];
char out_buffer[MAXSIZE];

const char *argp_program_version = "thread_buffer 1.0";
const char *argp_program_bug_address = "sidnei [at] enfoldsystems [dot] com";

static char doc[] =
  "An example of a buffered file copy using two threads";

static char args_doc[] = "SOURCE DEST";

static struct argp_option options[] = {
  {"verbose", 'v', 0, 0, "Produce verbose output"},
  { 0 }
};

struct arguments
{
  char *args[2];
  int verbose;
};

int finished = 0;
int verbose = 0;

size_t read_bytes;
size_t write_bytes;

sem_t sem_in;
sem_t sem_out;

pthread_t t_in;
pthread_t t_out;

pthread_mutex_t mutex;

static error_t
parse_opt(int key, char *arg, struct argp_state *state)
{
  struct arguments *arguments = state->input;

  switch (key)
    {
    case 'v':
      arguments->verbose = 1;
      break;

    case ARGP_KEY_ARG:
      if (state->arg_num >=2)
        argp_usage(state);

      arguments->args[state->arg_num] = arg;
      break;

    case ARGP_KEY_END:
      if (state->arg_num < 2)
        argp_usage(state);

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}


void read_in() {
  while (1){
    sem_wait(&sem_in);
    pthread_mutex_lock(&mutex);

    if (feof(in)){
      finished = 1;
      pthread_mutex_unlock(&mutex);
      sem_post(&sem_out);
      break;
    }
    pthread_mutex_unlock(&mutex);

    read_bytes = fread(in_buffer, sizeof(char), MAXSIZE, in);

    if (read_bytes && verbose){
      printf("read %d bytes\n", read_bytes);
    }

    pthread_mutex_lock(&mutex);

    write_bytes = read_bytes;
    memcpy(out_buffer, in_buffer, read_bytes);

    pthread_mutex_unlock(&mutex);
    sem_post(&sem_out);
  }
}

void write_out() {
  int break_out = 0;

  while (1) {
    sem_wait(&sem_out);
    pthread_mutex_lock(&mutex);

    if (finished) break_out = 1;
    pthread_mutex_unlock(&mutex);

    pthread_mutex_lock(&mutex);
    if (write_bytes){
      if (verbose)
        printf("writing %d bytes\n", write_bytes);
      fwrite(out_buffer, sizeof(char), write_bytes, out);
    }

    pthread_mutex_unlock(&mutex);
    sem_post(&sem_in);

    if (break_out) break;
  }
}

static struct argp argp = { options, parse_opt, args_doc, doc };

int main(int argc, char **argv)
{
  struct arguments arguments;

  arguments.verbose = 0;

  argp_parse(&argp, argc, argv, 0, 0, &arguments);

  verbose = arguments.verbose;

  in = fopen(arguments.args[0], "r");
  out = fopen(arguments.args[1], "w+");

  pthread_mutex_init(&mutex, NULL);
  sem_init(&sem_in, PTHREAD_PROCESS_PRIVATE, 1);
  sem_init(&sem_out, PTHREAD_PROCESS_PRIVATE, 0);

  pthread_create(&t_in, NULL, (void *) read_in, NULL);
  pthread_create(&t_out, NULL, (void *) write_out, NULL);

  pthread_join(t_in, NULL);
  pthread_join(t_out, NULL);

  pthread_mutex_destroy(&mutex);
  sem_destroy(&sem_in);
  sem_destroy(&sem_out);

  fclose(in);
  fclose(out);

}
